// Copyrights 2021 WU PENG. All rights reserved.
//

#include "max7219.h"

static const uint8_t NoOpReg        = 0x00;
static const uint8_t Digit0Reg      = 0x01;
static const uint8_t Digit1Reg      = 0x02;
static const uint8_t Digit2Reg      = 0x03;
static const uint8_t Digit3Reg      = 0x04;
static const uint8_t Digit4Reg      = 0x05;
static const uint8_t Digit5Reg      = 0x06;
static const uint8_t Digit6Reg      = 0x07;
static const uint8_t Digit7Reg      = 0x08;
static const uint8_t DecodeModeReg  = 0x09;
static const uint8_t IntensityReg   = 0x0A;
static const uint8_t ScanLimitReg   = 0x0B;
static const uint8_t ShutdownReg    = 0x0C;
static const uint8_t DisplayTestReg = 0x0F;

static const uint8_t fonts[1+10+26+26][8] = {
  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Space
  {0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00}, // 0
  {0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00}, // 1
  {0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8, 0x00}, // 2
  {0x70, 0x88, 0x08, 0x70, 0x08, 0x88, 0x70, 0x00}, // 3
  {0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10, 0x00}, // 4
  {0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70, 0x00}, // 5
  {0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70, 0x00}, // 6
  {0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40, 0x00}, // 7
  {0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00}, // 8
  {0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60, 0x00}, // 9
  {0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00}, // A
  {0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0, 0x00}, // B
  {0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70, 0x00}, // C
  {0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0, 0x00}, // D
  {0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8, 0x00}, // E
  {0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00}, // F
  {0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70, 0x00}, // G
  {0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00}, // H
  {0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00}, // I
  {0x38, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60, 0x00}, // J
  {0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00}, // K
  {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00}, // L
  {0x88, 0xD8, 0xA8, 0x88, 0x88, 0x88, 0x88, 0x00}, // M
  {0x88, 0xC8, 0xC8, 0xA8, 0x98, 0x98, 0x88, 0x00}, // N
  {0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00}, // O
  {0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80, 0x00}, // P
  {0x70, 0x88, 0x88, 0x88, 0x88, 0xA8, 0x78, 0x00}, // Q  
  {0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88, 0x00}, // R
  {0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00}, // S
  {0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00}, // T
  {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00}, // U
  {0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00}, // V
  {0x88, 0x88, 0xA8, 0xA8, 0xA8, 0xA8, 0x50, 0x00}, // W
  {0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00}, // X
  {0x88, 0x50, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00}, // Y
  {0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00}, // Z
  {0x00, 0x00, 0x60, 0x90, 0x90, 0x90, 0x68, 0x00}, // a
  {0x80, 0x80, 0x80, 0xE0, 0x90, 0x90, 0xE0, 0x00}, // b
  {0x00, 0x00, 0x70, 0x80, 0x80, 0x80, 0x70, 0x00}, // c
  {0x10, 0x10, 0x10, 0x70, 0x90, 0x90, 0x70, 0x00}, // d
  {0x00, 0x00, 0x60, 0x90, 0xF0, 0x80, 0x60, 0x00}, // e
  {0x00, 0x20, 0x40, 0xF0, 0x40, 0x40, 0x40, 0x00}, // f
  {0x00, 0x00, 0x60, 0x90, 0x90, 0x70, 0x10, 0x60}, // g
  {0x00, 0x80, 0x80, 0xE0, 0x90, 0x90, 0x90, 0x00}, // h
  {0x00, 0x20, 0x00, 0x60, 0x20, 0x20, 0x30, 0x00}, // i
  {0x00, 0x20, 0x00, 0x60, 0x20, 0x20, 0xA0, 0x40}, // j
  {0x00, 0x80, 0x80, 0xA0, 0xC0, 0xA0, 0x90, 0x00}, // k
  {0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x00}, // l
  {0x00, 0x00, 0xD0, 0xA8, 0xA8, 0xA8, 0xA8, 0x00}, // m
  {0x00, 0x00, 0xE0, 0x90, 0x90, 0x90, 0x90, 0x00}, // n
  {0x00, 0x00, 0x60, 0x90, 0x90, 0x90, 0x60, 0x00}, // o
  {0x00, 0x00, 0xE0, 0x90, 0x90, 0xE0, 0x80, 0x80}, // p
  {0x00, 0x00, 0x70, 0x90, 0x90, 0x70, 0x10, 0x10}, // q
  {0x00, 0x00, 0xE0, 0x90, 0x80, 0x80, 0x80, 0x00}, // r
  {0x00, 0x00, 0x70, 0x80, 0xF0, 0x10, 0xE0, 0x00}, // s
  {0x00, 0x40, 0x40, 0xF0, 0x40, 0x40, 0x30, 0x00}, // t
  {0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x70, 0x00}, // u
  {0x00, 0x00, 0x90, 0x90, 0x90, 0xA0, 0x40, 0x00}, // v
  {0x00, 0x00, 0x88, 0xA8, 0xA8, 0xA8, 0x50, 0x00}, // w
  {0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00}, // x
  {0x00, 0x00, 0x90, 0x50, 0x30, 0x10, 0x10, 0x60}, // y
  {0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8, 0x00}, // Z
};

Max7219::Max7219(int dinPin, int csPin, int clkPin, uint8_t blockNumber) {
  this->dinPin      = dinPin;
  this->clkPin      = clkPin;
  this->csPin       = csPin;
  this->blockNumber = blockNumber;

  buffer = new uint8_t[8*blockNumber];
}

Max7219::~Max7219() {
  delete buffer;
}

void Max7219::begin() {
  pinMode(dinPin, OUTPUT);
  pinMode(clkPin, OUTPUT);
  pinMode(csPin,  OUTPUT);

  SetDecodeMode(DecodeModeNone);
  SetIntensity(IntensityMin);
  SetScanLimit(ScanLimitAll);
  SetDisplay(DisplayNormal);
  SetPower(PowerOn);
  for (int i=0;i<blockNumber-1;i++) {
    SetNoOp();
  }
}

void Max7219::write(uint8_t addr, uint8_t value) {
  shiftOut(dinPin, clkPin, MSBFIRST, addr);
  shiftOut(dinPin, clkPin, MSBFIRST, value);
}

void Max7219::setRegister(uint8_t reg, uint8_t value) {
  digitalWrite(csPin, LOW);
  this->write(reg, value);
  digitalWrite(csPin, HIGH);
}

void Max7219::SetNoOp() {
  setRegister(NoOpReg, 0);
}

void Max7219::SetDecodeMode(uint8_t value) {
  setRegister(DecodeModeReg, value);
}

void Max7219::SetIntensity(uint8_t value) {
  if (value > 0x0F) {
    value = 0x0F;
  }
  setRegister(IntensityReg, value);
}


void Max7219::SetScanLimit(uint8_t value) {
  if (value > 0x07) {
    value = 0x07;
  }
  setRegister(ScanLimitReg, value);
}

void Max7219::SetPower(uint8_t value) {
  if (value > 1) {
    value = 1;
  }
  setRegister(ShutdownReg, value);
}

void Max7219::SetDisplay(uint8_t value) {
  if (value > 1) {
    value = 1;
  }
  setRegister(DisplayTestReg, value);
}

void Max7219::ClearBuffer() {
  for (int i=0;i<8*blockNumber;i++) {
    buffer[i] = 0;
  }
}

int Max7219::SetBuffer(uint8_t block, uint8_t addr, uint8_t value) {
  if (block >= blockNumber) {
    return -1;
  }

  buffer[block * 8 + addr] = value;

  return 0;
}

void Max7219::Flush() {
  for (int i=0;i<8;i++) {
    digitalWrite(csPin, LOW);
    for (int j=0;j<blockNumber;j++) {
      this->write(i+1, buffer[j*8+i]);
    }
    digitalWrite(csPin, HIGH);
  }
}

void Max7219::DrawText(uint8_t pos, char c) {
  uint8_t n = 0;
  if (c==' ') {
    n = c - ' ';
  } else if (c>='0' && c<='9') {
    n = c - '0' + 1;
  } else if (c>='A' && c<='Z') {
    n = c - 'A' + 1 + 10;
  } else if (c>='a' && c<='z') {
    n = c - 'a' + 1 + 10 + 26;
  }

  uint8_t left = 8 - (pos % 8);
  uint8_t right = pos % 8;
  for (int i=0;i<8;i++) {
    if (pos/8 < blockNumber) {
      buffer[pos/8*8+i] &= 0xff<<left;
      buffer[pos/8*8+i] |= fonts[n][i]>>right;
    }
    if (pos/8+1 < blockNumber) {
      buffer[(pos/8+1)*8+i] &= 0xff>>right;
      buffer[(pos/8+1)*8+i] |= fonts[n][i]<<left;
    }
  }
}
